package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.course.CategoryClient;
import com.tianji.api.dto.course.CategoryBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author Yandel
 * @since 2025-08-15
 */
@Slf4j
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Resource
    private ICouponScopeService couponScopeService;

    @Resource
    private IExchangeCodeService exchangeCodeService;

    /**
     * 调用远程服务
     */
    @Resource
    private CategoryClient categoryClient;

    @Resource
    private IUserCouponService userCouponService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void saveCoupon(CouponFormDTO dto) {
        // 1.保存优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        // 1.2.保存
        save(coupon);//直接保存优惠券到Coupon表
        //判断是否是限定的，如果不是限定，直接return
        if (!dto.getSpecific()) {
            // 没有范围限定
            return;
        }
        Long couponId = coupon.getId();

        // 2.保存限定范围
        List<Long> scopes = dto.getScopes();
        log.info("限定范围：{}", scopes);
        List<CategoryBasicDTO> allOfOneLevel = categoryClient.getAllOfOneLevel();
//        log.info("所有分类详情分类：{}", allOfOneLevel);
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        // 2.1.转换PO
        List<CouponScope> list = new ArrayList<>();
        for (Long bizId : scopes) {//遍历出每个bizId
            CouponScope scope = new CouponScope();//给中间表中的每个bizId生成CouponScope对象，并设置值
            scope.setBizId(bizId);
            scope.setCouponId(couponId);
            scope.setType(2);//写死课程类 【数据库设计字段有问题】
            list.add(scope);
        }
//                                                    遍历条件  ->  (每个条件的行为)给每个bizId生成CouponScope对象，并设置值
//        List<CouponScope> list = scopes.stream().map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId))
//                .collect(Collectors.toList());
        // 2.2.保存
        couponScopeService.saveBatch(list);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        Integer status = query.getStatus();
        String name = query.getName();
        Integer type = query.getType();
        //1.分页查询
        Page<Coupon> page = lambdaQuery()
                .eq(status != null, Coupon::getStatus, status)
                .eq(type != null, Coupon::getDiscountType, type)
                .like(StringUtils.isNotBlank(name), Coupon::getName, name)//使用hutool工具校验
                .page(query.toMpPageDefaultSortByCreateTimeDesc());//默认排序字段为创建时间倒序
        //2.处理vo
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        //将po转换成vo  （查询出来的东西比返回的东西多）
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);

        //3.返回
        return PageDTO.of(page, list);
    }


    @Transactional
    @Override
    public void beginIssue(CouponIssueFormDTO dto) {
        log.info("开始发放优惠券   线程名为：{}", Thread.currentThread().getName());
        //1.查询优惠券
        Coupon coupon = getById(dto.getId());
        //2.判断优惠券状态，是否是暂停或待发放
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE) {
            throw new BadRequestException("优惠券状态错误");
        }
        //3.判断是否是立刻发放
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegion = issueBeginTime == null || !issueBeginTime.isAfter(now);
        //4.更新优惠券
        //4.1.拷贝属性到PO
        Coupon c = BeanUtils.copyBean(dto, Coupon.class);
        //4.2.更新状态
        if (isBegion) {
            c.setStatus(CouponStatus.ISSUING);
            c.setIssueBeginTime(now);
        } else {
            c.setStatus(CouponStatus.UN_ISSUE);
        }
        //4.3.写入数据库
        updateById(c);

        //5、果优惠券是立可发放，将优惠券信息（优惠券id、领卷开始时间结束时间、发行总数、限领数量）写入Redis ，采用hash结构存储
        if (isBegion) {
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + c.getId();//prs:coupon: 优惠券id
            HashMap<String, String> map = new HashMap<>();
            map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
            map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            map.put("totalNum", String.valueOf(coupon.getTotalNum()));
            map.put("userLimit", String.valueOf(coupon.getUserLimit()));
            stringRedisTemplate.opsForHash().putAll(key, map);
        }


        // 6.判断是否需要生成兑换码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(c.getIssueEndTime());
            exchangeCodeService.asyncGenerateCode(coupon);
        }

    }

    @Override
    public void updateCoupon(Long id, CouponFormDTO dto) {
        //校验参数
        Long dtoId = dto.getId();
        if (dtoId != null && !dtoId.equals(id) || id == null && dtoId == null) {
            throw new BadRequestException("参数错误~");
        }
        //更新优惠券范围信息
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        //只更新状态为1的优惠券基本信息，如果失败则是状态已修改
        boolean update = lambdaUpdate().eq(Coupon::getStatus, CouponStatus.DRAFT).update(coupon);
        //本信息更新失败则无需更新优惠券范围信息
        if (!update) {
            return;
        }
        //更新优惠券范围信息(scope,记录其他业务范围的id)
        List<Long> scopeIds = dto.getScopes();
        //惠券不满减，或优惠券范围为空，则不更新优惠券范围信息
        //删除优惠券范围信息，再重新插入
        List<Long> ids = couponScopeService.lambdaQuery()
                .eq(CouponScope::getCouponId, dto.getId()).list()
                .stream().map(CouponScope::getId).collect(Collectors.toList());
        couponScopeService.removeByIds(ids);

        //插入新的优惠券范围信息
        if (CollUtils.isNotEmpty(scopeIds)) {
            List<CouponScope> list = scopeIds.stream()
                    .map(scopeId -> new CouponScope().setBizId(scopeId).setCouponId(dto.getId()).setType(1))
                    .collect(Collectors.toList());
            couponScopeService.saveBatch(list);
        }

    }

    @Override
    public void deleteCoupon(Long id) {
        if (id == null) {
            throw new BadRequestException("参数错误");
        }
        Coupon coupon = getById(id);
        if (coupon.getStatus() == CouponStatus.DRAFT) {
            removeById(id);
        }
    }

    @Override
    public CouponDetailVO getCouponById(Long id) {
        if (id == null) {
            throw new BadRequestException("参数错误");
        }
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        // 1. po 转 vo
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);

        // 2. 查询优惠券范围信息
        List<CouponScope> scopeList = couponScopeService.lambdaQuery()
                .eq(CouponScope::getCouponId, id)
                .list();

        // 3. 如果有范围限定，需要查询分类信息
        if (CollUtils.isNotEmpty(scopeList)) {
            // 获取所有分类信息
            List<CategoryBasicDTO> allCategories = categoryClient.getAllOfOneLevel();
            Map<Long, CategoryBasicDTO> categoryMap = allCategories.stream()
                    .collect(Collectors.toMap(CategoryBasicDTO::getId, category -> category));

            // 构建范围VO列表
            List<CouponScopeVO> scopeVOList = scopeList.stream()
                    .map(scope -> {
                        CategoryBasicDTO category = categoryMap.get(scope.getBizId());
                        return new CouponScopeVO(
                                category != null ? category.getId() : scope.getBizId(),
                                category != null ? category.getName() : "未知分类"
                        );
                    })
                    .collect(Collectors.toList());

            vo.setScopes(scopeVOList);
        }

        return vo;
    }


    @Override
    public void pauseIssue(long id) {
        //1、查询旧的优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        //2、当前优惠券必须是未开始或者进行中
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.UN_ISSUE && status!=CouponStatus.ISSUING){
            //状态错误，直接结束
            return;
        }

        //3、更新优惠券状态
        boolean success = this.lambdaUpdate()
                .eq(Coupon::getId, id)
                .set(Coupon::getStatus, CouponStatus.PAUSE)
                .in(Coupon::getStatus, CouponStatus.UN_ISSUE, CouponStatus.ISSUING)
                .update();
        if (!success){
            throw new BadRequestException("优惠券状态已变更，请刷新重试");
        }

        //4、删除缓存
        stringRedisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);
    }

    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1、   查询优惠券 条件：正在发放中的 / 手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();

        if (CollUtils.isEmpty(couponList)) {
            return CollUtils.emptyList();
        }

        //2、查询用户券表user_coupon 条件当前用户   发放中的优惠券id
        //正在发放中的优惠券id
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        //查询当前用户领取的正在发放中的优惠券
        List<UserCoupon> userCouponList = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        //2.1统计当前用户   针对每一个券   的已经领取的数量
        //方式一
/*        Map<Long,Long>  issueMap = new HashMap<>();//代表当前用户对每一个券的领取数量
        for (UserCoupon userCoupon : userCouponList) {
            Long num = issueMap.get(userCoupon.getCouponId());//优惠券领取数量
            if (num == null) {
                issueMap.put(userCoupon.getCouponId(), 1L);
            }else {
                issueMap.put(userCoupon.getCouponId(), num + 1);
            }
        }*/
        //方式二
        //键：优惠券id   值：领取数量
        Map<Long, Long> issueMap = userCouponList.stream()
                //计算couponId在userCouponList中出现的次数
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.2 统计当前用户  针对每一个券  的已经领取但是未使用的数量
        Map<Long, Long> unissueMap = userCouponList.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)//先过滤
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));//在统计

        //po 转 vo
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon coupon : couponList) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);

            Long issNum = issueMap.getOrDefault(coupon.getId(), 0L);
            boolean avaliable = coupon.getIssueNum() < coupon.getTotalNum() && issNum.intValue() < coupon.getUserLimit();
            vo.setAvailable(avaliable);//是否可以领取 优惠券还有剩余（issue_num<total_num）且（统计用户卷表user_coupon取出当前用户已经数量<user_limit)
            //统计用户券表user_coupon取出当前用户已经且未使用的卷数量
            boolean receive = unissueMap.getOrDefault(coupon.getId(), 0L) > 0;
            vo.setReceived(receive);//是否可以使用
            voList.add(vo);
        }

        return voList;
    }
}
